﻿using System;
using System.Xml;
using Microsoft.Win32;

namespace XmlDocumentRegistry
{
    public static class XmlDocumentExtension
    {
        public static void ImportFromRegistry(this XmlDocument doc, RegistryKey rootKey, string path)
        {
            var root = doc.CreateElement("registry");

            var rk = doc.CreateElement("k");
            rk.SetAttribute("name", rootKey.ToString());
            root.AppendChild(rk);

            var node = rk;
            foreach (var partName in path.Split(new[] { '\\', '/' }))
            {
                var k = doc.CreateElement("k");
                node.AppendChild(k);
                node = k;
                node.SetAttribute("name", partName);
            }

            var key = rootKey.OpenSubKey(path);
            Fill(node, key);
            doc.AppendChild(root);
        }

        private static void Fill(XmlElement node, RegistryKey key)
        {
            foreach (var valueName in key.GetValueNames())
            {
                var v = node.OwnerDocument.CreateElement("v");
                v.SetAttribute("name", valueName);

                switch (key.GetValueKind(valueName))
                {
                    case RegistryValueKind.String:
                        v.AppendChild(node.OwnerDocument.CreateCDataSection((string)key.GetValue(valueName)));
                        break;
                    case RegistryValueKind.DWord:
                        v.SetAttribute("type", "REG_DWORD");
                        v.SetAttribute("value", key.GetValue(valueName).ToString());
                        break;
                    case RegistryValueKind.Binary:
                        v.SetAttribute("type", "REG_BINARY");
                        v.SetAttribute("value", Convert.ToBase64String((byte[])key.GetValue(valueName)));
                        break;
                }

                node.AppendChild(v);
            }

            foreach (var subkeyName in key.GetSubKeyNames())
            {
                var k = node.OwnerDocument.CreateElement("k");
                k.SetAttribute("name", subkeyName);

                Fill(k, key.OpenSubKey(subkeyName));

                node.AppendChild(k);
            }
        }

        public static void ExportToRegistry(this XmlDocument doc)
        {
            RegistryKey key = null;

            switch (doc.DocumentElement.ChildNodes[0].Attributes["name"].Value)
            {
                case "HKEY_CLASSES_ROOT":
                    key = Registry.ClassesRoot;
                    break;
                case "HKEY_CURRENT_CONFIG":
                    key = Registry.ClassesRoot;
                    break;
                case "HKEY_CURRENT_USER":
                    key = Registry.CurrentUser;
                    break;
                case "HKEY_LOCAL_MACHINE":
                    key = Registry.LocalMachine;
                    break;
                case "HKEY_PERFORMANCE_DATA":
                    key = Registry.PerformanceData;
                    break;
                case "HKEY_USERS":
                    key = Registry.Users;
                    break;
                default:
                    throw new Exception("Unknown root registry key");
            }

            AddRegistryKeys(key, doc.DocumentElement.ChildNodes[0]);
        }

        private static void AddRegistryKeys(RegistryKey key, XmlNode node)
        {
            foreach (XmlElement child in node.ChildNodes)
            {
                var name = child.Attributes["name"].Value;

                if (child.Name == "k")
                {
                    var subkey = key.CreateSubKey(name);
                    AddRegistryKeys(subkey, child);
                }
                else if (child.Name == "v")
                {
                    if (child.HasAttribute("type"))
                    {
                        var value = child.Attributes["value"].Value;
                        switch (child.Attributes["type"].Value)
                        {
                            case "REG_DWORD":
                                key.SetValue(name, int.Parse(value));
                                break;
                            case "REG_BINARY":
                                key.SetValue(name, Convert.FromBase64String(value));
                                break;
                        }
                    }
                    else
                    {
                        key.SetValue(name, child.InnerText);
                    }
                }
            }
        }
    }
}
